package controllers;

import java.util.LinkedList;
import java.util.List;

import notifiers.Mails;

//import notifiers.Mails;

import models.Component;
import models.ComponentTaskTypeUser;
import models.Log;
import models.Notification;
import models.Project;
import models.Task;
import models.TaskStatus;
import models.TaskType;
import models.TaskUser;
import models.User;
import play.libs.Mail;
import play.mvc.Controller;
import play.mvc.With;

/**
 * The controller that handles all the actions related to TaskTypes.
 */
@With(Secure.class)
public class TaskTypes extends Controller {

	public static void index(long projectId) {
		Project project = Project.findById(projectId);
		List<TaskType> taskTypes = project.taskTypes;
		List<TaskStatus> taskStatuses = project.taskStatuses;
		render(project, taskTypes, taskStatuses);

	}

	/**
	 * Displays the page that allows the user to choose the task type they would
	 * like to review in the project with the given projectId.
	 * 
	 * @param projectId
	 *            The id of the project
	 * @author Rawan
	 */
	public static void showRequestReview(final Long projectId) {
		Security.checkAccess("review a task", projectId, Project.class
				.getName());
		Project project = Project.findById(projectId);

		List<TaskType> allTypes = project.availableTaskTypes();
		render(allTypes, project);
	}

	/**
	 * This method takes the request from the view and calls the method that
	 * will handle the request depending on whether the request is for the
	 * project or the component.
	 * 
	 * @param taskTypeIds
	 *            The id's of all the task types that the user wants to review.
	 * @param reviewFor
	 *            Whether the review is for the whole project or a specific
	 *            component.
	 * @param componentIds
	 *            The id's of the components that the user will review for. This
	 *            will be null in the case that the review is for the whole
	 *            project.
	 * @param projectId
	 *            The id of the project.
	 * @author Rawan
	 */
	public static void submitRequestReview(final long[] taskTypeIds,
			final String reviewFor, final long[] componentIds,
			final long projectId) {
		Security.checkAccess("review a task", projectId, Project.class
				.getName());
		if (taskTypeIds != null) {
			User reviewer = User
					.findById(Long.parseLong(session.get("userId")));
			Project project = Project.findById(projectId);
			List<ComponentTaskTypeUser> allRequests = ComponentTaskTypeUser
					.findAll();
			List<User> requestApprovers = Users.getUsersWithPermission(
					"approve task type review requests", (Long) project
							.getEntityId());
			String message = "";
			if (reviewFor.equals("project")) {
				message = requestReviewForProjects(taskTypeIds, reviewer,
						project, allRequests, requestApprovers);
			} else {
				if (componentIds != null) {
					message = requestReviewForComponents(taskTypeIds,
							componentIds, project, reviewer, allRequests,
							requestApprovers);
				} else {
					flash.error("Error! You must select a component!");
					showRequestReview(projectId);
				}
			}
			if (message.equals("")) {
				flash.success("Request sent successfully! You will be "
						+ "notified once the requests are approved.");
			} else {
				message = message.substring(0, message.length() - 2);
				flash.error("You have already requested to review "
						+ "the task types: " + message
						+ ". However, requests for reviewing "
						+ "other task types you selected (if any) "
						+ "have been sent successfully!");
			}
			// Projects.showProject(projectId);
			Tasks.showAllProjectTasks(projectId);
		} else {
			flash.error("Error! You must select a task type!");
			showRequestReview(projectId);
		}
	}

	/**
	 * This method handles the requests to review some task types for the
	 * project. It will call the method requestReviewForProject for each task
	 * type in the list and return either a success message or a message
	 * containing all the task types that have already been requested. In the
	 * second case, any requests that were not already sent previously will be
	 * successfully added.
	 * 
	 * @param taskTypeIds
	 *            The list of id's for the task types requested.
	 * @param reviewer
	 *            The User that is sending the request.
	 * @param project
	 *            The project for which the request is added.
	 * @param allRequests
	 *            The list of all the task type review requests currently in the
	 *            system.
	 * @param requestApprovers
	 *            The list of users who have the permission to approve the
	 *            request.
	 * @return String
	 * @author Rawan
	 */
	public static String requestReviewForProjects(final long[] taskTypeIds,
			final User reviewer, final Project project,
			final List<ComponentTaskTypeUser> allRequests,
			final List<User> requestApprovers) {
		String message = "";
		for (int i = 0; i < taskTypeIds.length; i++) {
			TaskType type = TaskType.findById(taskTypeIds[i]);
			String msg = requestReviewForProject(type, reviewer,
					requestApprovers, project, allRequests);
			if (!msg.equals("Success!")) {
				message += msg + ", ";
			}
		}
		return message;
	}

	/**
	 * Connects with the Model to add the request to review the given task type
	 * to the database. It returns a String either a success message or a string
	 * with the name of the task type to indicate that it has already been
	 * requested. After the record is added, it calls the method sendRequest to
	 * notify the users who can approve the request about the request.
	 * 
	 * @param type
	 *            The task type which is requested to be reviewed.
	 * @param reviewer
	 *            The User that is sending the request.
	 * @param project
	 *            The project for which the request is added.
	 * @param allRequests
	 *            The list of all the task type review requests currently in the
	 *            system.
	 * @param requestApprovers
	 *            The list of users who have the permission to approve the
	 *            request.
	 * @return String
	 * @author Rawan
	 */
	public static String requestReviewForProject(final TaskType type,
			final User reviewer, final List<User> requestApprovers,
			final Project project, final List<ComponentTaskTypeUser> allRequests) {
		Component defaultC = null;
		for (int i = 0; i < project.components.size(); i++) {
			if (project.components.get(i).name
					.equalsIgnoreCase("default component")) {
				defaultC = project.components.get(i);
				break;
			}
		}
		ComponentTaskTypeUser cttu = new ComponentTaskTypeUser(reviewer, type,
				defaultC);
		boolean contains = alreadyRequested(allRequests, project, reviewer,
				type, null);
		if (!contains) {
			cttu.save();
			new Log((Long) reviewer.getEntityId(), (Long) cttu.getEntityId(),
					"ComponentTaskTypeUser",
					"Requested to review the task type \"" + type.type
							+ "\" for the project \"" + project.name + "\".",
					" ");
			if (requestApprovers != null) {
				String message = "\" for the project \"" + project.name + "\"";
				sendReviewRequest(message, reviewer, requestApprovers, type);
			}
			return "Success!";
		}
		return "\"" + type.type + "\"";
	}

	/**
	 * Checks whether the request has been previously made or not. Returns true
	 * if the request already exists in the database.
	 * 
	 * @param allRequests
	 *            The list of all the task type review requests currently in the
	 *            system.
	 * @param reviewer
	 *            The User that is sending the request.
	 * @param project
	 *            The project for which the request is added.
	 * @param type
	 *            The task type which is requested to be reviewed.
	 * @param component
	 *            The component for which the request is made.
	 * 
	 * @return boolean
	 * @author Rawan
	 */
	public static boolean alreadyRequested(
			final List<ComponentTaskTypeUser> allRequests,
			final Project project, final User reviewer, final TaskType type,
			final Component component) {
		Component defaultC = null;
		for (int i1 = 0; i1 < project.components.size(); i1++) {
			if (project.components.get(i1).name
					.equalsIgnoreCase("default component")) {
				defaultC = project.components.get(i1);
				break;
			}
		}
		if (component != null) {
			for (int i = 0; i < allRequests.size(); i++) {
				ComponentTaskTypeUser cttuCurrent = allRequests.get(i);
				if (cttuCurrent.taskType.getEntityId().equals(
						type.getEntityId())
						&& ((cttuCurrent.component.getEntityId()
								.equals(component.getEntityId())) || (cttuCurrent.component
								.getEntityId().equals(defaultC.getEntityId()) && cttuCurrent.status))
						&& cttuCurrent.reviewer.getEntityId().equals(
								reviewer.getEntityId())) {
					return true;
				}
			}
		} else {
			for (int i = 0; i < allRequests.size(); i++) {
				ComponentTaskTypeUser cttuCurrent = allRequests.get(i);
				if (cttuCurrent.taskType.getEntityId().equals(
						type.getEntityId())
						&& cttuCurrent.reviewer.getEntityId().equals(
								reviewer.getEntityId())
						&& cttuCurrent.component.getEntityId().equals(
								defaultC.getEntityId())) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * This method handles the requests to review some task types for certain
	 * components. It will call the method requestReviewForComponent for each
	 * task type and component in the list and return either a success message
	 * or a message containing all the task types that have already been
	 * requested. In the second case, any requests that were not already sent
	 * previously will be successfully added.
	 * 
	 * @param taskTypeIds
	 *            The list of id's for the task types requested.
	 * @param componentIds
	 *            The list of id's of the components for which the request is
	 *            made.
	 * @param reviewer
	 *            The User that is sending the request.
	 * @param project
	 *            The project for which the request is added.
	 * @param allRequests
	 *            The list of all the task type review requests currently in the
	 *            system.
	 * @param requestApprovers
	 *            The list of users who have the permission to approve the
	 *            request.
	 * @return String
	 * @author Rawan
	 */
	public static String requestReviewForComponents(final long[] taskTypeIds,
			final long[] componentIds, final Project project,
			final User reviewer, final List<ComponentTaskTypeUser> allRequests,
			final List<User> requestApprovers) {
		String message = "";
		for (int i = 0; i < taskTypeIds.length; i++) {
			TaskType type = TaskType.findById(taskTypeIds[i]);
			for (int j = 0; j < componentIds.length; j++) {
				Component component = Component.findById(componentIds[j]);
				String msg = requestReviewForComponent((Long) component
						.getEntityId(), type, reviewer, requestApprovers,
						project, allRequests);
				if (!msg.equals("Success!")) {
					message += msg + ", ";
				}
			}
		}
		return message;
	}

	/**
	 * Connects with the Model to add the request to review the given task type
	 * for a component to the database. It returns either a success message or a
	 * string with the name of the task type to indicate that it has already
	 * been requested. After the record is added, it calls the method
	 * sendRequest to notify the users who can approve the request about the
	 * request.
	 * 
	 * @param componentId
	 *            The id of the component to which the request is made.
	 * @param type
	 *            The task type that will be reviewed.
	 * @param reviewer
	 *            The user who requested the review.
	 * @param requestApprovers
	 *            The list of users who have permission to approve the requests.
	 * @param project
	 *            The project in which the request is made.
	 * @param allRequests
	 *            The list of all requests in the system.
	 * @return String
	 * @author Rawan
	 */
	public static String requestReviewForComponent(final long componentId,
			final TaskType type, final User reviewer,
			final List<User> requestApprovers, final Project project,
			final List<ComponentTaskTypeUser> allRequests) {
		Component component = Component.findById(componentId);
		ComponentTaskTypeUser cttu = new ComponentTaskTypeUser(reviewer, type,
				component);
		boolean contains = alreadyRequested(allRequests, project, reviewer,
				type, component);
		if (!contains) {
			cttu.save();
			new Log((Long) reviewer.getEntityId(), (Long) cttu.getEntityId(),
					"ComponentTaskTypeUser",
					"Requested to review the task type \"" + type.type
							+ "\" for the component \"" + component.name
							+ "\" in the project \"" + project.name + "\".",
					" ");
			if (requestApprovers != null) {
				String message = "\" for the component \"" + component.name
						+ "\" in the project \"" + project.name + "\"";
				sendReviewRequest(message, reviewer, requestApprovers, type);
			}
			return "Success!";
		}
		return "\"" + type.type + "\"";
	}

	/**
	 * Send an email and a notification message to each of the users who have
	 * the permission to approve requests.
	 * 
	 * @param message
	 *            The message to be sent.
	 * @param reviewer
	 *            The user who sent the request.
	 * @param requestApprovers
	 *            The list of users who have permission to accept the request.
	 * @param type
	 *            The task type to be requested.
	 * @return String
	 * @author Rawan
	 */
	private static void sendReviewRequest(final String message,
			final User reviewer, final List<User> requestApprovers,
			final TaskType type) {
		String msg = String.format(reviewer.firstName + " " + reviewer.lastName
				+ " has requested to review the task type: \"" + type.type
				+ message + ". Waiting for your approval.");
		Notifications.notifyAllUsers(requestApprovers, "Review Request", msg);
		for (int j = 0; j < requestApprovers.size(); j++) {
			Mails.notifyTaskTypeReviewRequest(requestApprovers.get(j), msg);
		}
	}

	/**
	 * Calls the method rejectReviewRequestHelper. Notifies the user who made
	 * the request that the request has been rejected.
	 * 
	 * @param requestId
	 *            The id of the request that will be removed
	 */
	public static void rejectReviewRequest(final long requestId) {
		ComponentTaskTypeUser rejectedRequest = ComponentTaskTypeUser
				.findById(requestId);
		Security.checkAccess("approve task type review requests",
				(Long) rejectedRequest.component.project.getEntityId(),
				Project.class.getName());
		sendReviewResponse(rejectedRequest, false);
		new Log(Long.parseLong(session.get("userId")), requestId,
				"ComponentTaskTypeUser", "Rejected a request by \""
						+ rejectedRequest.reviewer.userName
						+ "\" to become a reviewer for the task type \""
						+ rejectedRequest.taskType.type
						+ "\". The requested was deleted from the system.",
				(Long) rejectedRequest.component.project.getEntityId(), " ")
				.save();
		Project proj = rejectedRequest.component.project;
		rejectReviewRequestHelper(rejectedRequest);
		Tasks.showTaskReviewRequests((Long) proj.getEntityId());
	}

	/**
	 * Removes the request that has been denied from the system.
	 * 
	 * @param rejectedRequest
	 *            The request to be deleted.
	 * @return boolean
	 */
	public static boolean rejectReviewRequestHelper(
			ComponentTaskTypeUser rejectedRequest) {
		rejectedRequest.delete();
		return true;
	}

	/**
	 * Calls the method acceptRequestReviewHelper with the request being
	 * accepted. The method sendReviewResponse is then called to notify the
	 * reviewer of the status of the request.
	 * 
	 * @param requestId
	 *            The id of the request that will be removed
	 */
	public static void acceptReviewRequest(final long requestId) {
		ComponentTaskTypeUser acceptedRequest = ComponentTaskTypeUser
				.findById(requestId);
		Security.checkAccess("approve task type review requests",
				(Long) acceptedRequest.component.project.getEntityId(),
				Project.class.getName());
		sendReviewResponse(acceptedRequest, true);
		new Log(Long.parseLong(session.get("userId")), requestId,
				"ComponentTaskTypeUser", "Accepted a request by \""
						+ acceptedRequest.reviewer.userName
						+ "\" to become a reviewer for the task type \""
						+ acceptedRequest.taskType.type + "\".",
				(Long) acceptedRequest.component.project.getEntityId(), " ")
				.save();
		acceptedRequest.acceptReviewRequestHelper();
		Project proj = acceptedRequest.component.project;
		Tasks.showTaskReviewRequests((Long) proj.getEntityId());
	}

	/**
	 * Sends a notification to the user who made the request to review the task
	 * type about the status of his request once it is accepted or rejected.
	 * 
	 * @param request
	 *            The request that was responded to.
	 * @param accepted
	 *            Whether the request was accepted or rejected.
	 */
	private static void sendReviewResponse(final ComponentTaskTypeUser request,
			final boolean accepted) {

		String message = "Your request to review the task type \""
				+ request.taskType.type + "\" for the project \""
				+ request.component.project.name + "\"";
		if (!request.component.name.equalsIgnoreCase("default component")) {
			message += " for the component \"" + request.component.name + "\"";
		}
		if (accepted) {
			message += " has been accepted!";
		} else {
			message += " has been denied!";
		}
		new Notification((Long) request.reviewer.getEntityId(),
				"Review Request " + ((accepted) ? "Accepted" : "Denied"),
				message).save();
		Mails.notifyTaskTypeReviewResponse(request.reviewer, message,
				"Review Request " + ((accepted) ? "Accepted" : "Denied"));

	}

	public static void renderChangeTaskTypeName(long projectId, long id,
			String name, boolean isSystem) {
		changeTaskTypeName(projectId, id, name, isSystem);
		if (isSystem)
			Tasks.index();
		else
			TaskTypes.index(projectId);
	}

	/**
	 * Given a task type id and a name, this method changes the name of the Task
	 * Type with the given id. No duplicate names allowed.
	 * 
	 * @param id
	 * @param name
	 * @return boolean
	 * 
	 */
	public static boolean changeTaskTypeName(long projectId, long id,
			String name, boolean isSystem) {
		if (!isSystem)
			Security.checkAccess("Create/Edit/delete task type in my project",
					projectId, Project.class.getName());
		TaskType type = TaskType.findById(id);
		Project project = Project.findById(projectId);
		List<TaskType> list = new LinkedList<TaskType>();
		if (isSystem)
			list = TaskType.all().fetch();
		else
			list = project.taskTypes;

		boolean found = false;
		int size = list.size();
		for (int i = 0; i < size; i++) {
			if (list.get(i).type.equals(name))
				found = true;
		}
		if (!found) {
			type.changeName(name);
			return true;
		} else
			return false;

	}

	/**
	 * Given a task type name, a project id, this method creates a new task type
	 * and adds it to the system. The type is added to the given project if the
	 * isSystem is false. No duplicate names allowed.
	 * 
	 * @param projectId
	 * @param name
	 * @param isSystem
	 * 
	 * @return boolean
	 * 
	 */
	public static boolean createTaskType(long projectId, String name,
			boolean isSystem) {

		if (!isSystem)
			Security.checkAccess("Create/Edit/delete task type in my project",
					projectId, Project.class.getName());
		Project project = Project.findById(projectId);
		boolean found = false;
		if (!isSystem) {

			for (int i = 0; i < project.taskTypes.size(); i++)
				if (project.taskTypes.get(i).type.equals(name))
					found = true;

			if (!found && !name.equals("") && name != null) {
				TaskType type = new TaskType(name, project);
				type.save();
				project.taskTypes.add(type);
				project.save();
				return true;
			} else
				return false;

		} else {
			List<TaskType> list = TaskType.getAllTaskTypes();
			int size = list.size();
			for (int i = 0; i < size; i++)
				if (list.get(i).type.equals(name))
					found = true;

			TaskType type = new TaskType(name, isSystem);
			if (!found && !name.equals("") && name != null) {
				type.save();
				return true;
			} else {
				return false;
			}
		}

	}

	public static void renderCreateTaskType(long projectId, String name,
			boolean isSystem) {
		createTaskType(projectId, name, isSystem);
		if (isSystem)
			Tasks.index();
		else
			TaskTypes.index(projectId);
	}

	public static void renderDeleteTaskType(long id, boolean isSystem) {
		TaskType type = TaskType.findById(id);
		long projectId = -1;
		if (!isSystem) {
			projectId = (Long) type.project.getEntityId();
			Security.checkAccess("Create/Edit/delete task type in my project",
					projectId, Project.class.getName());
		}
		type.delete();
		if (isSystem)
			Tasks.index();
		else
			TaskTypes.index(projectId);
	}

	/**
	 * Renders the form to edit a specific type.
	 * 
	 * @param taskId
	 */
	public static void showTaskType(long id) {
		TaskType type = TaskType.findById(id);
		Project project = type.project;
		render(type, project);
	}

}
